Load libraries
#### Load packages ####
library(camprotR)
library(ggplot2)
library(tidyverse)
library(MSnbase)
library(biobroom)
Read in the PSM data
psm_res <- readRDS('../results/psm_res.rds')
psm_res %>% names() %>% lapply(function(x){
psm_res[[x]] %>%
fData() %>%
mutate(method=x)
}) %>%
do.call(what='rbind') %>%
group_by(method) %>%
summarise(median_sn=10^median(log10(Average.Reporter.SN), na.rm=TRUE))
`summarise()` ungrouping output (override with `.groups` argument)
129.15/42
[1] 3.075
p <- psm_res %>% names() %>% lapply(function(x){
psm_res[[x]] %>%
fData() %>%
mutate(method=x)
}) %>%
do.call(what='rbind') %>%
ggplot(aes(Average.Reporter.SN, colour=method)) +
geom_density() +
theme_camprot()
print(p)

print(p + scale_x_log10())

print(p + aes(Isolation.Interference.in.Percent))

Plotting the proportion of missing values
psm_res %>% names() %>% lapply(function(x){
all <- psm_res[[x]]
hs <- all[fData(all)$species=='H.sapiens']
sc <- all[fData(all)$species=='S.cerevisiae']
slices <- list('All'=all, 'H.sapiens'=hs, 'S.cerevisiae'=sc)
for(slice in names(slices)){
p <- slices[[slice]] %>% plot_missing_SN() +
ggtitle(sprintf('%s - %s', x, slice))
print(p)
p <- slices[[slice]] %>% plot_missing_SN_per_sample() +
ggtitle(sprintf('%s - %s', x, slice))
print(p)
}
return(NULL)
})
[[1]]
NULL
[[2]]
NULL












OK, so essentially all the missing values are restricted to PSMs with low (<20) Signal:Noise ratios.
source('./R/get_quant_vs_mean.R')
quant_vs_mean <- psm_res %>% lapply(get_quant_vs_mean)
quant_vs_mean <- quant_vs_mean %>% lapply(function(x){
x %>%
mutate(binned_interference=Hmisc::cut2(
Isolation.Interference.in.Percent, cuts=c(0,1,5,10,seq(20,100,20))),
binned_q=Hmisc::cut2(Percolator.q.Value, cuts=c(0, 0.001, 0.002, 0.005, 0.01)),
binned_average_sn=Hmisc::cut2(Average.Reporter.SN, cuts=c(0,10,20,30,40,60,100)),
binned_intensity=Hmisc::cut2(intensity, cuts=c(0,10,20,30,40,60,100)))
})
quant_vs_mean %>% lapply(dim)
$`AGC: 2E5`
[1] 1980560 23
$`AGC: 5E4`
[1] 2166658 23
exp_design <- pData(psm_res$`AGC: 2E5`) %>%
select(condition, S.cerevisiae=yeast, H.sapiens=human) %>%
unique()
sc_spikes <- exp_design$S.cerevisiae
hs_spikes <- exp_design$H.sapiens
get_ground_truth <- function(sc_spikes, hs_spikes, ix_1, ix_2){
comparison <- sprintf('%s vs %s', sc_spikes[ix_2], sc_spikes[ix_1])
hs_ground_truth <- hs_spikes[ix_2]/hs_spikes[ix_1]
sc_ground_truth <- sc_spikes[ix_2]/sc_spikes[ix_1]
return(c(comparison, hs_ground_truth, sc_ground_truth))
}
library(gtools)
expected <- apply(permutations(n=3,r=2), 1, function(x){
get_ground_truth(sc_spikes, hs_spikes, x[1], x[2])
}) %>% t() %>% data.frame() %>%
setNames(c('comparison', 'H.sapiens', 'S.cerevisiae')) %>%
mutate_at(vars(S.cerevisiae,
H.sapiens),
funs(as.numeric)) %>%
pivot_longer(-comparison, names_to='species', values_to='expected')
print(expected)
positive_comparisons <- expected %>% filter(species=='S.cerevisiae', expected>1) %>%
pull(comparison)
quant_vs_mean %>% names() %>% lapply(function(x){
for(sp in c("S.cerevisiae", "H.sapiens")){
p <- quant_vs_mean[[x]] %>%
filter(species==sp, comparison %in% positive_comparisons) %>%
ggplot(aes(binned_interference, diff, colour=below_notch)) +
geom_violin() +
theme_camprot(base_size=10) +
theme(axis.text.x=element_text(angle=45, vjust=1, hjust=1)) +
facet_wrap(~comparison, scales='free') +
geom_hline(aes(yintercept=log2(expected)),
data=expected[(expected$species==sp &
expected$comparison %in% positive_comparisons),],
colour='grey', linetype=2) +
xlab('Interference') +
ylab('Difference in intensity') +
theme(aspect.ratio=.3) +
ggtitle(x)
print(p)
}
p <- quant_vs_mean[[x]] %>%
filter(species=='S.cerevisiae', Isolation.Interference.in.Percent<=60,
comparison %in% positive_comparisons) %>%
ggplot(aes(diff, colour=binned_interference)) +
geom_density() +
theme_camprot(base_size=10) +
theme(axis.text.x=element_text(angle=45, vjust=1, hjust=1)) +
facet_grid(below_notch~comparison, scales='free') +
geom_vline(aes(xintercept=log2(expected)),
data=expected[(expected$species=='S.cerevisiae' &
expected$comparison %in% positive_comparisons),],
colour='grey', linetype=2) +
xlab('Difference in intensity') +
ylab('Density') +
xlim(-6,3) +
theme(aspect.ratio=2) +
ggtitle(x)
print(p)
})
[[1]]
[[2]]








quant_vs_mean %>% names() %>% lapply(function(x){
p <- quant_vs_mean[[x]] %>%
filter(Isolation.Interference.in.Percent<=50, # no need to consider interference>=50%
comparison %in% positive_comparisons) %>%
filter(species=='S.cerevisiae', !below_notch) %>%
ggplot(aes(diff, colour=binned_intensity)) +
geom_density() +
theme_camprot(base_size=15) +
theme(axis.text.x=element_text(angle=45, vjust=1, hjust=1)) +
facet_grid(binned_interference~comparison, scales='free') +
geom_vline(aes(xintercept=log2(expected)),
data=expected[(expected$species=='S.cerevisiae' &
expected$comparison %in% positive_comparisons),],
colour=get_cat_palette(1), linetype=2) +
ylab('Density') +
xlab('Difference in intensity') +
xlim(-6,3) +
ggtitle(x) +
scale_colour_discrete(name='Intensity')
print(p)
print(p + aes(colour=binned_interference) + facet_grid(binned_average_sn~comparison) +
scale_colour_discrete(name='Interference (%)'))
print(p + aes(colour=binned_q) + scale_colour_discrete(name='Percolator Q'))
print(p + aes(colour=binned_interference) + facet_grid(binned_q~comparison) +
scale_colour_discrete(name='Interference (%)'))
return(NULL)
})
[[1]]
NULL
[[2]]
NULL








quant_vs_mean %>% names() %>% lapply(function(x){
p <- quant_vs_mean[[x]] %>%
filter(Isolation.Interference.in.Percent<=60) %>% # no need to consider interference>=60%
filter(species=='S.cerevisiae', !below_notch, comparison=='6 vs 1') %>%
group_by(binned_interference, binned_intensity) %>%
summarise(median_diff=2^median(diff, na.rm=TRUE), n=length(diff)) %>%
ggplot(aes(binned_interference, binned_intensity, fill=median_diff)) +
geom_tile(colour='grey') +
theme_camprot(base_size=15) +
scale_fill_gradient(high=get_cat_palette(2)[2],
low='white',
limits=c(0, 6), name='Observed\nfold change') +
theme(axis.text.x=element_text(angle=45, vjust=1, hjust=1)) +
xlab('Binned interference') +
ylab('Binned intensity') +
ggtitle(x)
print(p + geom_text(aes(label=round(median_diff, 1)), size=3))
print(p +
aes(fill=n) +
scale_fill_gradient(high=get_cat_palette(3)[3],
low='white') +
geom_text(aes(label=n), size=3) )
})
`summarise()` regrouping output by 'binned_interference' (override with `.groups` argument)
[[1]]
[[2]]






quant_vs_mean %>% names() %>% lapply(function(x){
quant_vs_mean[[x]] %>%
filter(Isolation.Interference.in.Percent<=60) %>% # no need to consider interference>=60%
filter(species=='S.cerevisiae', !below_notch, comparison=='6 vs 1') %>%
group_by(binned_q, binned_interference, binned_intensity) %>%
summarise(median_diff=2^median(diff, na.rm=TRUE)) %>%
ggplot(aes(binned_interference, binned_intensity, fill=median_diff)) +
geom_tile(colour='grey') +
facet_wrap(~binned_q) +
theme_camprot(base_size=15) +
scale_fill_gradient2(high=get_cat_palette(2)[2],
low=get_cat_palette(2)[1],
mid='white', midpoint=0,
limits=c(-1, 6), name='Observed\nfold change') +
theme(axis.text.x=element_text(angle=45, vjust=1, hjust=1),
panel.background=element_rect(fill="grey")) +
xlab('Binned interference') +
ylab('Binned Intensity') +
ggtitle(x)
})
`summarise()` regrouping output by 'binned_q', 'binned_interference' (override with `.groups` argument)
`summarise()` regrouping output by 'binned_q', 'binned_interference' (override with `.groups` argument)
[[1]]
[[2]]


quant_vs_mean %>% names() %>% lapply(function(x){
p <- quant_vs_mean[[x]] %>%
select(id, species, binned_q, binned_average_sn, binned_interference) %>%
unique() %>%
group_by(species, binned_q, binned_average_sn, binned_interference) %>%
tally() %>%
ggplot(aes(binned_interference, n)) +
geom_bar(stat='identity') +
facet_wrap(~species, scales='free') +
theme_camprot(base_size=15) +
theme(axis.text.x=element_text(angle=45, vjust=1, hjust=1)) +
ggtitle(x)
print(p)
print(p + aes(binned_q))
print(p + aes(binned_average_sn) +
xlab('Signal/Noise'))
return(NULL)
})
[[1]]
NULL
[[2]]
NULL






Based on the above, I’m going to use the following thresholds:
- Isolation interference <= 10%
- Percolator Q value <= 0.001
For now, we won’t filer using SN since we want to explore the impact of the notch on fold changes in more detail first
First though, let’s filter by Percolator Q value or Isolation interence alone and check how isolation interference affects PSM-level fold change estimates.
quant_vs_mean %>% names() %>% lapply(function(x){
to_plot_q_flt <- quant_vs_mean[[x]] %>%
filter(Percolator.q.Value<=0.005) %>%
filter(species=='S.cerevisiae') %>%
filter(Isolation.Interference.in.Percent<50) %>%
arrange(Isolation.Interference.in.Percent)
to_plot_interference_flt <- quant_vs_mean[[x]] %>%
filter(Isolation.Interference.in.Percent<=10) %>%
filter(species=='S.cerevisiae') %>%
arrange(Percolator.q.Value)
p <- to_plot_q_flt %>%
ggplot(aes(log2(intensity), diff)) +
geom_point(size=0.1, alpha=0.1, colour='grey80') +
theme_camprot(base_size=12) +
facet_wrap(~comparison, scales='free_y') +
geom_hline(aes(yintercept=log2(expected)),
data=expected[expected$species=='S.cerevisiae',],
colour='black', linetype=2) +
xlab('Tag intensity (log2)') +
ylab('Difference in intensity (log2)') +
scale_colour_manual(values=c(get_cat_palette(7)),
name='Isolation interference (%)') +
ggtitle(x)
print(p)
print(p + geom_smooth(aes(colour=binned_interference), se=FALSE, size=0.5))
print(p %+% to_plot_interference_flt +
geom_smooth(aes(colour=binned_q), se=FALSE, size=0.5) +
scale_colour_manual(values=c(get_cat_palette(7)),
name='Percolator Q value'))
return(NULL)
})
[[1]]
NULL
[[2]]
NULL






Let’s plot the intensity vs difference in intensity for all comparisons and filtering schemes.
quant_vs_mean %>% names() %>% lapply(function(x){
tmp_data <- quant_vs_mean[[x]] %>%
filter(species!='mixed', !comparison %in% positive_comparisons)
p <- tmp_data %>%
ggplot(aes(log2(intensity), diff)) +
geom_point(size=0.05, alpha=0.05, colour='grey10') +
geom_density2d(size=0.2, colour=get_cat_palette(2)[2]) +
theme_camprot(base_size=12) +
facet_grid(species~comparison, scales='free_y') +
geom_hline(aes(yintercept=log2(expected)),
data=expected[!expected$comparison %in% positive_comparisons,],
colour='black', linetype=2) +
xlab('Tag intensity (log2)') +
ylab('Difference in intensity (log2)') +
ggtitle(x) +
coord_cartesian(ylim=c(-4,4))
print(p)
stop()
print(p %+% (tmp_data %>%
filter(Percolator.q.Value<=0.005)))
print(p %+% (tmp_data %>%
filter(Isolation.Interference.in.Percent<=10)))
print(p %+% (tmp_data %>%
filter(Percolator.q.Value<=0.005, Isolation.Interference.in.Percent<=10)))
print(p %+% (tmp_data %>%
filter(Percolator.q.Value<=0.005, Isolation.Interference.in.Percent<=10, Average.Reporter.SN>10)))
return(NULL)
})
Error in FUN(X[[i]], ...) :

Now, let’s filter the PSMs against these thresholds.
psm_res_flt <- psm_res %>% lapply(function(x){
out <- filter_TMT_PSMs(x, inter_thresh=20, sn_thresh=0)
out <- out[fData(out)$Percolator.q.Value<=0.001,]
camprotR:::message_parse(fData(out),
'Master.Protein.Accessions',
"Percolator Q value filtering")
out
})
Filtering PSMs...
99650 features found from 10512 master proteins => No quant filtering
72364 features found from 9674 master proteins => Co-isolation filtering
72364 features found from 9674 master proteins => S:N ratio filtering
56814 features found from 8696 master proteins => Percolator Q value filtering
Filtering PSMs...
109197 features found from 10974 master proteins => No quant filtering
77629 features found from 10061 master proteins => Co-isolation filtering
77629 features found from 10061 master proteins => S:N ratio filtering
62610 features found from 9164 master proteins => Percolator Q value filtering
psm_res_flt_sn <- psm_res_flt %>% lapply(function(x){
out <- filter_TMT_PSMs(x, inter_thresh=20, sn_thresh=10)
out
})
Filtering PSMs...
56814 features found from 8696 master proteins => No quant filtering
56814 features found from 8696 master proteins => Co-isolation filtering
55292 features found from 8619 master proteins => S:N ratio filtering
Filtering PSMs...
62610 features found from 9164 master proteins => No quant filtering
62610 features found from 9164 master proteins => Co-isolation filtering
57787 features found from 8965 master proteins => S:N ratio filtering
psm_res %>% names() %>% lapply(function(x){
datasets <- list('unfiltered'=psm_res, 'filtered'=psm_res_flt, '\nfiltered, inc S/N'=psm_res_flt_sn)
for(dataset in names(datasets)){
all <- datasets[[dataset]][[x]]
hs <- all[fData(all)$species=='H.sapiens']
sc <- all[fData(all)$species=='S.cerevisiae']
slices <- list('All'=all, 'H.sapiens'=hs, 'S.cerevisiae'=sc)
for(slice in names(slices)){
p <- slices[[slice]] %>% plot_TMT_notch() +
ggtitle(sprintf('%s - %s - %s', x, slice, dataset))
print(p)
}
}
return(NULL)
})
[[1]]
NULL
[[2]]
NULL


















Per-tag notch plots
psm_res %>% names() %>% lapply(function(x){
datasets <- list('unfiltered'=psm_res, 'filtered'=psm_res_flt, '\nfiltered, inc S/N'=psm_res_flt_sn)
for(dataset in names(datasets)){
all <- datasets[[dataset]][[x]]
hs <- all[fData(all)$species=='H.sapiens']
sc <- all[fData(all)$species=='S.cerevisiae']
slices <- list('All'=all, 'H.sapiens'=hs, 'S.cerevisiae'=sc)
for(slice in names(slices)){
p <- slices[[slice]] %>% plot_TMT_notch(facet_by_sample=TRUE) +
ggtitle(sprintf('%s - %s - %s', x, slice, dataset))
print(p)
}
}
return(NULL)
})
[[1]]
NULL
[[2]]
NULL


















Tallies for fraction sub-notch PSMs per protein
psm_res %>% names() %>% lapply(function(x){
datasets <- list('unfiltered'=psm_res, 'filtered'=psm_res_flt, '\nfiltered, inc S/N'=psm_res_flt_sn)
for(dataset in names(datasets)){
all <- datasets[[dataset]][[x]]
hs <- all[fData(all)$species=='H.sapiens']
sc <- all[fData(all)$species=='S.cerevisiae']
slices <- list('All'=all, 'H.sapiens'=hs, 'S.cerevisiae'=sc)
for(slice in names(slices)){
notch_per_protein <- get_notch_per_protein(slices[[slice]]) %>%
filter(fraction_below>0)
p <- plot_fraction_below_notch_per_prot(notch_per_protein) +
ggtitle(sprintf('%s - %s - %s', x, slice, dataset))
print(p)
}
}
return(NULL)
})
[[1]]
NULL
[[2]]
NULL


















Missing values frequencies.
psm_res %>% names() %>% lapply(function(x){
datasets <- list('unfiltered'=psm_res, 'filtered'=psm_res_flt, '\nfiltered, inc S/N'=psm_res_flt_sn)
for(dataset in names(datasets)){
all <- datasets[[dataset]][[x]]
hs <- all[fData(all)$species=='H.sapiens']
sc <- all[fData(all)$species=='S.cerevisiae']
slices <- list('All'=all, 'H.sapiens'=hs, 'S.cerevisiae'=sc)
for(slice in names(slices)){
plotNA(slices[[slice]], pNA = 0)
}
}
return(NULL)
})
[[1]]
NULL
[[2]]
NULL


















Save out objects for downstream notebooks
saveRDS(quant_vs_mean, '../results/quant_vs_mean.rds')
saveRDS(psm_res_flt, '../results/psm_res_flt.rds')
saveRDS(psm_res_flt_sn, '../results/psm_res_flt_sn.rds')
saveRDS(expected, '../results/expected.rds')
LS0tCnRpdGxlOiAnRmlsdGVyIFBTTXMnCmF1dGhvcjoKICAtIG5hbWU6ICJUb20gU21pdGgiCiAgICBhZmZpbGlhdGlvbjogIkNhbWJyaWRnZSBDZW50cmUgZm9yIFByb3Rlb21pY3MiCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkICVCLCAlWScpYCIKYWJzdHJhY3Q6IHwgCiAgSGVyZSwgd2UgZmlsdGVyIHRoZSBQU00tbGV2ZWwgUEQgb3V0cHV0LCB3aXRoIHRocmVzaG9sZHMgaW5mb3JtZWQgYnkgbWlzc2luZyB2YWx1ZXMsCiAgbm90Y2ggcHJvbWluZW5jZSBhbmQgb2JzZXJ2ZWQgZm9sZCBjaGFuZ2VzIHZzIGdyb3VuZCB0cnV0aHMuCm91dHB1dDoKICBwZGZfZG9jdW1lbnQ6CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdApnZW9tZXRyeTogbWFyZ2luPTFpbgpmb250ZmFtaWx5OiBtYXRocGF6bwpmb250c2l6ZTogMTFwdAotLS0KCkxvYWQgbGlicmFyaWVzCmBgYHtyIHNldHVwLCBtZXNzYWdlPUZBTFNFfQoKIyMjIyBMb2FkIHBhY2thZ2VzICMjIyMKbGlicmFyeShjYW1wcm90UikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShNU25iYXNlKQpsaWJyYXJ5KGJpb2Jyb29tKQpgYGAKClJlYWQgaW4gdGhlIFBTTSBkYXRhCmBgYHtyfQpwc21fcmVzIDwtIHJlYWRSRFMoJy4uL3Jlc3VsdHMvcHNtX3Jlcy5yZHMnKQpgYGAKCmBgYHtyfQpwc21fcmVzICU+JSBuYW1lcygpICU+JSBsYXBwbHkoZnVuY3Rpb24oeCl7CiAgcHNtX3Jlc1tbeF1dICU+JQogICAgZkRhdGEoKSAlPiUKICAgIG11dGF0ZShtZXRob2Q9eCkKfSkgJT4lCiAgZG8uY2FsbCh3aGF0PSdyYmluZCcpICU+JQogIGdyb3VwX2J5KG1ldGhvZCkgJT4lCiAgc3VtbWFyaXNlKG1lZGlhbl9zbj0xMF5tZWRpYW4obG9nMTAoQXZlcmFnZS5SZXBvcnRlci5TTiksIG5hLnJtPVRSVUUpKQoxMjkuMTUvNDIKYGBgCgpgYGB7cn0KcCA8LSBwc21fcmVzICU+JSBuYW1lcygpICU+JSBsYXBwbHkoZnVuY3Rpb24oeCl7CiAgcHNtX3Jlc1tbeF1dICU+JQogICAgZkRhdGEoKSAlPiUKICAgIG11dGF0ZShtZXRob2Q9eCkKfSkgJT4lCiAgZG8uY2FsbCh3aGF0PSdyYmluZCcpICU+JQogIGdncGxvdChhZXMoQXZlcmFnZS5SZXBvcnRlci5TTiwgY29sb3VyPW1ldGhvZCkpICsKICAgIGdlb21fZGVuc2l0eSgpICsKICAgIHRoZW1lX2NhbXByb3QoKQoKcHJpbnQocCkKcHJpbnQocCArIHNjYWxlX3hfbG9nMTAoKSkKCnByaW50KHAgKyBhZXMoSXNvbGF0aW9uLkludGVyZmVyZW5jZS5pbi5QZXJjZW50KSkKYGBgCgpQbG90dGluZyB0aGUgcHJvcG9ydGlvbiBvZiBtaXNzaW5nIHZhbHVlcyAKYGBge3J9Cgpwc21fcmVzICU+JSBuYW1lcygpICU+JSBsYXBwbHkoZnVuY3Rpb24oeCl7CiAgCiAgYWxsIDwtIHBzbV9yZXNbW3hdXQogIGhzIDwtIGFsbFtmRGF0YShhbGwpJHNwZWNpZXM9PSdILnNhcGllbnMnXQogIHNjIDwtIGFsbFtmRGF0YShhbGwpJHNwZWNpZXM9PSdTLmNlcmV2aXNpYWUnXQogIAogIHNsaWNlcyA8LSBsaXN0KCdBbGwnPWFsbCwgJ0guc2FwaWVucyc9aHMsICdTLmNlcmV2aXNpYWUnPXNjKQogIGZvcihzbGljZSBpbiBuYW1lcyhzbGljZXMpKXsKICAgIHAgPC0gc2xpY2VzW1tzbGljZV1dICU+JSBwbG90X21pc3NpbmdfU04oKSArCiAgICAgIGdndGl0bGUoc3ByaW50ZignJXMgLSAlcycsIHgsIHNsaWNlKSkKICAgIHByaW50KHApCiAgCiAgICBwIDwtIHNsaWNlc1tbc2xpY2VdXSAlPiUgcGxvdF9taXNzaW5nX1NOX3Blcl9zYW1wbGUoKSArCiAgICAgIGdndGl0bGUoc3ByaW50ZignJXMgLSAlcycsIHgsIHNsaWNlKSkKICAgIHByaW50KHApCiAgfQogIHJldHVybihOVUxMKQp9KQpgYGAKT0ssIHNvIGVzc2VudGlhbGx5IGFsbCB0aGUgbWlzc2luZyB2YWx1ZXMgYXJlIHJlc3RyaWN0ZWQgdG8gUFNNcyB3aXRoIGxvdyAoPDIwKSBTaWduYWw6Tm9pc2UgcmF0aW9zLgoKYGBge3J9Cgpzb3VyY2UoJy4vUi9nZXRfcXVhbnRfdnNfbWVhbi5SJykKCgpxdWFudF92c19tZWFuIDwtIHBzbV9yZXMgJT4lIGxhcHBseShnZXRfcXVhbnRfdnNfbWVhbikKCnF1YW50X3ZzX21lYW4gPC0gcXVhbnRfdnNfbWVhbiAlPiUgbGFwcGx5KGZ1bmN0aW9uKHgpewogIHggJT4lCiAgICBtdXRhdGUoYmlubmVkX2ludGVyZmVyZW5jZT1IbWlzYzo6Y3V0MigKICAgICAgSXNvbGF0aW9uLkludGVyZmVyZW5jZS5pbi5QZXJjZW50LCBjdXRzPWMoMCwxLDUsMTAsc2VxKDIwLDEwMCwyMCkpKSwKICAgICAgYmlubmVkX3E9SG1pc2M6OmN1dDIoUGVyY29sYXRvci5xLlZhbHVlLCBjdXRzPWMoMCwgMC4wMDEsIDAuMDAyLCAwLjAwNSwgMC4wMSkpLAogICAgICBiaW5uZWRfYXZlcmFnZV9zbj1IbWlzYzo6Y3V0MihBdmVyYWdlLlJlcG9ydGVyLlNOLCBjdXRzPWMoMCwxMCwyMCwzMCw0MCw2MCwxMDApKSwKICAgICAgYmlubmVkX2ludGVuc2l0eT1IbWlzYzo6Y3V0MihpbnRlbnNpdHksIGN1dHM9YygwLDEwLDIwLDMwLDQwLDYwLDEwMCkpKQp9KQpgYGAKCgpgYGB7cn0KcXVhbnRfdnNfbWVhbiAlPiUgbGFwcGx5KGRpbSkKYGBgCgoKCmBgYHtyfQpleHBfZGVzaWduIDwtIHBEYXRhKHBzbV9yZXMkYEFHQzogMkU1YCkgJT4lCiAgc2VsZWN0KGNvbmRpdGlvbiwgUy5jZXJldmlzaWFlPXllYXN0LCBILnNhcGllbnM9aHVtYW4pICU+JQogIHVuaXF1ZSgpCiAgCnNjX3NwaWtlcyA8LSBleHBfZGVzaWduJFMuY2VyZXZpc2lhZQpoc19zcGlrZXMgPC0gZXhwX2Rlc2lnbiRILnNhcGllbnMKCmdldF9ncm91bmRfdHJ1dGggPC0gZnVuY3Rpb24oc2Nfc3Bpa2VzLCBoc19zcGlrZXMsIGl4XzEsIGl4XzIpewogIGNvbXBhcmlzb24gPC0gc3ByaW50ZignJXMgdnMgJXMnLCBzY19zcGlrZXNbaXhfMl0sIHNjX3NwaWtlc1tpeF8xXSkKICBoc19ncm91bmRfdHJ1dGggPC0gaHNfc3Bpa2VzW2l4XzJdL2hzX3NwaWtlc1tpeF8xXQogIHNjX2dyb3VuZF90cnV0aCA8LSBzY19zcGlrZXNbaXhfMl0vc2Nfc3Bpa2VzW2l4XzFdCiAgcmV0dXJuKGMoY29tcGFyaXNvbiwgaHNfZ3JvdW5kX3RydXRoLCBzY19ncm91bmRfdHJ1dGgpKQp9CmxpYnJhcnkoZ3Rvb2xzKQoKZXhwZWN0ZWQgPC0gYXBwbHkocGVybXV0YXRpb25zKG49MyxyPTIpLCAxLCBmdW5jdGlvbih4KXsKICBnZXRfZ3JvdW5kX3RydXRoKHNjX3NwaWtlcywgaHNfc3Bpa2VzLCB4WzFdLCB4WzJdKQp9KSAlPiUgdCgpICU+JSBkYXRhLmZyYW1lKCkgJT4lCiAgc2V0TmFtZXMoYygnY29tcGFyaXNvbicsICdILnNhcGllbnMnLCAnUy5jZXJldmlzaWFlJykpICU+JQogIG11dGF0ZV9hdCh2YXJzKFMuY2VyZXZpc2lhZSwgCiAgICAgICAgICAgICAgICAgSC5zYXBpZW5zKSwgCiAgICAgICAgICAgIGZ1bnMoYXMubnVtZXJpYykpICU+JQogIHBpdm90X2xvbmdlcigtY29tcGFyaXNvbiwgbmFtZXNfdG89J3NwZWNpZXMnLCB2YWx1ZXNfdG89J2V4cGVjdGVkJykKCnByaW50KGV4cGVjdGVkKQoKcG9zaXRpdmVfY29tcGFyaXNvbnMgPC0gZXhwZWN0ZWQgJT4lIGZpbHRlcihzcGVjaWVzPT0nUy5jZXJldmlzaWFlJywgZXhwZWN0ZWQ+MSkgJT4lCiAgcHVsbChjb21wYXJpc29uKQpgYGAKYGBge3J9CnF1YW50X3ZzX21lYW4gJT4lIG5hbWVzKCkgJT4lIGxhcHBseShmdW5jdGlvbih4KXsKICBmb3Ioc3AgaW4gYygiUy5jZXJldmlzaWFlIiwgIkguc2FwaWVucyIpKXsKICAgIHAgPC0gcXVhbnRfdnNfbWVhbltbeF1dICU+JQogICAgICBmaWx0ZXIoc3BlY2llcz09c3AsIGNvbXBhcmlzb24gJWluJSBwb3NpdGl2ZV9jb21wYXJpc29ucykgJT4lCiAgICAgIGdncGxvdChhZXMoYmlubmVkX2ludGVyZmVyZW5jZSwgZGlmZiwgY29sb3VyPWJlbG93X25vdGNoKSkgKwogICAgICBnZW9tX3Zpb2xpbigpICsKICAgICAgdGhlbWVfY2FtcHJvdChiYXNlX3NpemU9MTApICsKICAgICAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlPTQ1LCB2anVzdD0xLCBoanVzdD0xKSkgKwogICAgICBmYWNldF93cmFwKH5jb21wYXJpc29uLCBzY2FsZXM9J2ZyZWUnKSArCiAgICAgIGdlb21faGxpbmUoYWVzKHlpbnRlcmNlcHQ9bG9nMihleHBlY3RlZCkpLAogICAgICAgICAgICAgICAgIGRhdGE9ZXhwZWN0ZWRbKGV4cGVjdGVkJHNwZWNpZXM9PXNwICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV4cGVjdGVkJGNvbXBhcmlzb24gJWluJSBwb3NpdGl2ZV9jb21wYXJpc29ucyksXSwKICAgICAgICAgICAgICAgICBjb2xvdXI9J2dyZXknLCBsaW5ldHlwZT0yKSArCiAgICAgIHhsYWIoJ0ludGVyZmVyZW5jZScpICsKICAgICAgeWxhYignRGlmZmVyZW5jZSBpbiBpbnRlbnNpdHknKSArCiAgICAgIHRoZW1lKGFzcGVjdC5yYXRpbz0uMykgKwogICAgICBnZ3RpdGxlKHgpCiAgICAKICAgIHByaW50KHApCiAgfQogIAogIHAgPC0gcXVhbnRfdnNfbWVhbltbeF1dICU+JQogICAgZmlsdGVyKHNwZWNpZXM9PSdTLmNlcmV2aXNpYWUnLCBJc29sYXRpb24uSW50ZXJmZXJlbmNlLmluLlBlcmNlbnQ8PTYwLAogICAgICAgICAgIGNvbXBhcmlzb24gJWluJSBwb3NpdGl2ZV9jb21wYXJpc29ucykgJT4lCiAgICBnZ3Bsb3QoYWVzKGRpZmYsIGNvbG91cj1iaW5uZWRfaW50ZXJmZXJlbmNlKSkgKwogICAgZ2VvbV9kZW5zaXR5KCkgKwogICAgdGhlbWVfY2FtcHJvdChiYXNlX3NpemU9MTApICsKICAgIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChhbmdsZT00NSwgdmp1c3Q9MSwgaGp1c3Q9MSkpICsKICAgIGZhY2V0X2dyaWQoYmVsb3dfbm90Y2h+Y29tcGFyaXNvbiwgc2NhbGVzPSdmcmVlJykgKwogICAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdD1sb2cyKGV4cGVjdGVkKSksCiAgICAgICAgICAgICAgIGRhdGE9ZXhwZWN0ZWRbKGV4cGVjdGVkJHNwZWNpZXM9PSdTLmNlcmV2aXNpYWUnICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBleHBlY3RlZCRjb21wYXJpc29uICVpbiUgcG9zaXRpdmVfY29tcGFyaXNvbnMpLF0sCiAgICAgICAgICAgICAgIGNvbG91cj0nZ3JleScsIGxpbmV0eXBlPTIpICsKICAgIHhsYWIoJ0RpZmZlcmVuY2UgaW4gaW50ZW5zaXR5JykgKwogICAgeWxhYignRGVuc2l0eScpICsKICAgIHhsaW0oLTYsMykgKwogICAgdGhlbWUoYXNwZWN0LnJhdGlvPTIpICsKICAgIGdndGl0bGUoeCkKICAKICBwcmludChwKQp9KQpgYGAKYGBge3IsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTgsIHdhcm5pbmc9RkFMU0V9CnF1YW50X3ZzX21lYW4gJT4lIG5hbWVzKCkgJT4lIGxhcHBseShmdW5jdGlvbih4KXsKICBwIDwtIHF1YW50X3ZzX21lYW5bW3hdXSAlPiUKICAgIGZpbHRlcihJc29sYXRpb24uSW50ZXJmZXJlbmNlLmluLlBlcmNlbnQ8PTUwLCAjIG5vIG5lZWQgdG8gY29uc2lkZXIgaW50ZXJmZXJlbmNlPj01MCUKICAgICAgICAgICBjb21wYXJpc29uICVpbiUgcG9zaXRpdmVfY29tcGFyaXNvbnMpICU+JSAKICAgIGZpbHRlcihzcGVjaWVzPT0nUy5jZXJldmlzaWFlJywgIWJlbG93X25vdGNoKSAlPiUKICAgIGdncGxvdChhZXMoZGlmZiwgY29sb3VyPWJpbm5lZF9pbnRlbnNpdHkpKSArCiAgICBnZW9tX2RlbnNpdHkoKSArCiAgICB0aGVtZV9jYW1wcm90KGJhc2Vfc2l6ZT0xNSkgKwogICAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlPTQ1LCB2anVzdD0xLCBoanVzdD0xKSkgKwogICAgZmFjZXRfZ3JpZChiaW5uZWRfaW50ZXJmZXJlbmNlfmNvbXBhcmlzb24sIHNjYWxlcz0nZnJlZScpICsKICAgIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQ9bG9nMihleHBlY3RlZCkpLAogICAgICAgICAgICAgICBkYXRhPWV4cGVjdGVkWyhleHBlY3RlZCRzcGVjaWVzPT0nUy5jZXJldmlzaWFlJyAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXhwZWN0ZWQkY29tcGFyaXNvbiAlaW4lIHBvc2l0aXZlX2NvbXBhcmlzb25zKSxdLAogICAgICAgICAgICAgICBjb2xvdXI9Z2V0X2NhdF9wYWxldHRlKDEpLCBsaW5ldHlwZT0yKSArCiAgICB5bGFiKCdEZW5zaXR5JykgKwogICAgeGxhYignRGlmZmVyZW5jZSBpbiBpbnRlbnNpdHknKSArCiAgICB4bGltKC02LDMpICsKICAgIGdndGl0bGUoeCkgKwogICAgc2NhbGVfY29sb3VyX2Rpc2NyZXRlKG5hbWU9J0ludGVuc2l0eScpCiAgCiAgcHJpbnQocCkKICBwcmludChwICsgYWVzKGNvbG91cj1iaW5uZWRfaW50ZXJmZXJlbmNlKSArIGZhY2V0X2dyaWQoYmlubmVkX2F2ZXJhZ2Vfc25+Y29tcGFyaXNvbikgKwogICAgc2NhbGVfY29sb3VyX2Rpc2NyZXRlKG5hbWU9J0ludGVyZmVyZW5jZSAoJSknKSkKICAKICBwcmludChwICsgYWVzKGNvbG91cj1iaW5uZWRfcSkgKyBzY2FsZV9jb2xvdXJfZGlzY3JldGUobmFtZT0nUGVyY29sYXRvciBRJykpCiAgcHJpbnQocCArIGFlcyhjb2xvdXI9YmlubmVkX2ludGVyZmVyZW5jZSkgKyBmYWNldF9ncmlkKGJpbm5lZF9xfmNvbXBhcmlzb24pICsKICAgICAgICAgIHNjYWxlX2NvbG91cl9kaXNjcmV0ZShuYW1lPSdJbnRlcmZlcmVuY2UgKCUpJykpCgogIAogIHJldHVybihOVUxMKQp9KQpgYGAKCmBgYHtyfQpxdWFudF92c19tZWFuICU+JSBuYW1lcygpICU+JSBsYXBwbHkoZnVuY3Rpb24oeCl7CiAgICBwIDwtIHF1YW50X3ZzX21lYW5bW3hdXSAlPiUKICAgIGZpbHRlcihJc29sYXRpb24uSW50ZXJmZXJlbmNlLmluLlBlcmNlbnQ8PTYwKSAlPiUgIyBubyBuZWVkIHRvIGNvbnNpZGVyIGludGVyZmVyZW5jZT49NjAlCiAgICBmaWx0ZXIoc3BlY2llcz09J1MuY2VyZXZpc2lhZScsICFiZWxvd19ub3RjaCwgY29tcGFyaXNvbj09JzYgdnMgMScpICU+JQogICAgZ3JvdXBfYnkoYmlubmVkX2ludGVyZmVyZW5jZSwgYmlubmVkX2ludGVuc2l0eSkgJT4lCiAgICBzdW1tYXJpc2UobWVkaWFuX2RpZmY9Ml5tZWRpYW4oZGlmZiwgbmEucm09VFJVRSksIG49bGVuZ3RoKGRpZmYpKSAlPiUKICAgIGdncGxvdChhZXMoYmlubmVkX2ludGVyZmVyZW5jZSwgYmlubmVkX2ludGVuc2l0eSwgZmlsbD1tZWRpYW5fZGlmZikpICsKICAgIGdlb21fdGlsZShjb2xvdXI9J2dyZXknKSArCiAgICB0aGVtZV9jYW1wcm90KGJhc2Vfc2l6ZT0xNSkgKwogICAgc2NhbGVfZmlsbF9ncmFkaWVudChoaWdoPWdldF9jYXRfcGFsZXR0ZSgyKVsyXSwKICAgICAgICAgICAgICAgICAgICAgICAgbG93PSd3aGl0ZScsCiAgICAgICAgICAgICAgICAgICAgICAgIGxpbWl0cz1jKDAsIDYpLCBuYW1lPSdPYnNlcnZlZFxuZm9sZCBjaGFuZ2UnKSArCiAgICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X3RleHQoYW5nbGU9NDUsIHZqdXN0PTEsIGhqdXN0PTEpKSArCiAgICB4bGFiKCdCaW5uZWQgaW50ZXJmZXJlbmNlJykgKwogICAgeWxhYignQmlubmVkIGludGVuc2l0eScpICsKICAgIGdndGl0bGUoeCkKICAgIAogICAgcHJpbnQocCArIGdlb21fdGV4dChhZXMobGFiZWw9cm91bmQobWVkaWFuX2RpZmYsIDEpKSwgc2l6ZT0zKSkKICAgIAogICAgcHJpbnQocCArCiAgICAgICAgICAgIGFlcyhmaWxsPW4pICsKICAgICAgICAgICAgc2NhbGVfZmlsbF9ncmFkaWVudChoaWdoPWdldF9jYXRfcGFsZXR0ZSgzKVszXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsb3c9J3doaXRlJykgKwogICAgICAgICAgICBnZW9tX3RleHQoYWVzKGxhYmVsPW4pLCBzaXplPTMpICkKfSkKYGBgCgpgYGB7ciwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9Nn0KcXVhbnRfdnNfbWVhbiAlPiUgbmFtZXMoKSAlPiUgbGFwcGx5KGZ1bmN0aW9uKHgpewogICAgcXVhbnRfdnNfbWVhbltbeF1dICU+JQogICAgZmlsdGVyKElzb2xhdGlvbi5JbnRlcmZlcmVuY2UuaW4uUGVyY2VudDw9NjApICU+JSAjIG5vIG5lZWQgdG8gY29uc2lkZXIgaW50ZXJmZXJlbmNlPj02MCUKICAgIGZpbHRlcihzcGVjaWVzPT0nUy5jZXJldmlzaWFlJywgIWJlbG93X25vdGNoLCBjb21wYXJpc29uPT0nNiB2cyAxJykgJT4lCiAgICBncm91cF9ieShiaW5uZWRfcSwgYmlubmVkX2ludGVyZmVyZW5jZSwgYmlubmVkX2ludGVuc2l0eSkgJT4lCiAgICBzdW1tYXJpc2UobWVkaWFuX2RpZmY9Ml5tZWRpYW4oZGlmZiwgbmEucm09VFJVRSkpICU+JQogICAgZ2dwbG90KGFlcyhiaW5uZWRfaW50ZXJmZXJlbmNlLCBiaW5uZWRfaW50ZW5zaXR5LCBmaWxsPW1lZGlhbl9kaWZmKSkgKwogICAgZ2VvbV90aWxlKGNvbG91cj0nZ3JleScpICsKICAgIGZhY2V0X3dyYXAofmJpbm5lZF9xKSArCiAgICB0aGVtZV9jYW1wcm90KGJhc2Vfc2l6ZT0xNSkgKwogICAgc2NhbGVfZmlsbF9ncmFkaWVudDIoaGlnaD1nZXRfY2F0X3BhbGV0dGUoMilbMl0sCiAgICAgICAgICAgICAgICAgICAgICAgICBsb3c9Z2V0X2NhdF9wYWxldHRlKDIpWzFdLAogICAgICAgICAgICAgICAgICAgICAgICAgbWlkPSd3aGl0ZScsIG1pZHBvaW50PTAsCiAgICAgICAgICAgICAgICAgICAgICAgIGxpbWl0cz1jKC0xLCA2KSwgbmFtZT0nT2JzZXJ2ZWRcbmZvbGQgY2hhbmdlJykgKwogICAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlPTQ1LCB2anVzdD0xLCBoanVzdD0xKSwKICAgICAgICAgIHBhbmVsLmJhY2tncm91bmQ9ZWxlbWVudF9yZWN0KGZpbGw9ImdyZXkiKSkgKwogICAgeGxhYignQmlubmVkIGludGVyZmVyZW5jZScpICsKICAgIHlsYWIoJ0Jpbm5lZCBJbnRlbnNpdHknKSArCiAgICBnZ3RpdGxlKHgpCn0pCmBgYAoKYGBge3J9CgpxdWFudF92c19tZWFuICU+JSBuYW1lcygpICU+JSBsYXBwbHkoZnVuY3Rpb24oeCl7CiAgcCA8LSBxdWFudF92c19tZWFuW1t4XV0gJT4lCiAgICBzZWxlY3QoaWQsIHNwZWNpZXMsIGJpbm5lZF9xLCBiaW5uZWRfYXZlcmFnZV9zbiwgYmlubmVkX2ludGVyZmVyZW5jZSkgJT4lCiAgICB1bmlxdWUoKSAlPiUKICAgIGdyb3VwX2J5KHNwZWNpZXMsIGJpbm5lZF9xLCBiaW5uZWRfYXZlcmFnZV9zbiwgYmlubmVkX2ludGVyZmVyZW5jZSkgJT4lCiAgICB0YWxseSgpICU+JQogICAgZ2dwbG90KGFlcyhiaW5uZWRfaW50ZXJmZXJlbmNlLCBuKSkgKwogICAgZ2VvbV9iYXIoc3RhdD0naWRlbnRpdHknKSArCiAgICBmYWNldF93cmFwKH5zcGVjaWVzLCBzY2FsZXM9J2ZyZWUnKSArCiAgICB0aGVtZV9jYW1wcm90KGJhc2Vfc2l6ZT0xNSkgKwogICAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlPTQ1LCB2anVzdD0xLCBoanVzdD0xKSkgKwogICAgZ2d0aXRsZSh4KQogIAogIHByaW50KHApCiAgcHJpbnQocCArIGFlcyhiaW5uZWRfcSkpCiAgcHJpbnQocCArIGFlcyhiaW5uZWRfYXZlcmFnZV9zbikgKwogICAgICAgICAgeGxhYignU2lnbmFsL05vaXNlJykpCiAgCiAgcmV0dXJuKE5VTEwpCn0pCmBgYAoKCkJhc2VkIG9uIHRoZSBhYm92ZSwgSSdtIGdvaW5nIHRvIHVzZSB0aGUgZm9sbG93aW5nIHRocmVzaG9sZHM6CgotIElzb2xhdGlvbiBpbnRlcmZlcmVuY2UgPD0gMTAlCi0gUGVyY29sYXRvciBRIHZhbHVlIDw9IDAuMDAxCgpGb3Igbm93LCB3ZSB3b24ndCBmaWxlciB1c2luZyBTTiBzaW5jZSB3ZSB3YW50IHRvIGV4cGxvcmUgdGhlIGltcGFjdCBvZiB0aGUgbm90Y2ggb24gZm9sZCBjaGFuZ2VzIGluIG1vcmUgZGV0YWlsIGZpcnN0CgpGaXJzdCB0aG91Z2gsIGxldCdzIGZpbHRlciBieSBQZXJjb2xhdG9yIFEgdmFsdWUgb3IgSXNvbGF0aW9uIGludGVyZW5jZSBhbG9uZSBhbmQgY2hlY2sgaG93IGlzb2xhdGlvbiBpbnRlcmZlcmVuY2UgYWZmZWN0cyBQU00tbGV2ZWwgZm9sZCBjaGFuZ2UgZXN0aW1hdGVzLgpgYGB7ciwgZmlnLmhlaWdodD05LCBmaWcud2lkdGg9OX0KcXVhbnRfdnNfbWVhbiAlPiUgbmFtZXMoKSAlPiUgbGFwcGx5KGZ1bmN0aW9uKHgpewogIAogIHRvX3Bsb3RfcV9mbHQgPC0gcXVhbnRfdnNfbWVhbltbeF1dICU+JQogICAgZmlsdGVyKFBlcmNvbGF0b3IucS5WYWx1ZTw9MC4wMDUpICU+JQogICAgZmlsdGVyKHNwZWNpZXM9PSdTLmNlcmV2aXNpYWUnKSAlPiUKICAgIGZpbHRlcihJc29sYXRpb24uSW50ZXJmZXJlbmNlLmluLlBlcmNlbnQ8NTApICU+JQogICAgYXJyYW5nZShJc29sYXRpb24uSW50ZXJmZXJlbmNlLmluLlBlcmNlbnQpCiAgCiAgdG9fcGxvdF9pbnRlcmZlcmVuY2VfZmx0IDwtIHF1YW50X3ZzX21lYW5bW3hdXSAlPiUKICAgIGZpbHRlcihJc29sYXRpb24uSW50ZXJmZXJlbmNlLmluLlBlcmNlbnQ8PTEwKSAlPiUKICAgIGZpbHRlcihzcGVjaWVzPT0nUy5jZXJldmlzaWFlJykgJT4lCiAgICBhcnJhbmdlKFBlcmNvbGF0b3IucS5WYWx1ZSkKICAKICBwIDwtIHRvX3Bsb3RfcV9mbHQgJT4lCiAgICBnZ3Bsb3QoYWVzKGxvZzIoaW50ZW5zaXR5KSwgZGlmZikpICsKICAgIGdlb21fcG9pbnQoc2l6ZT0wLjEsIGFscGhhPTAuMSwgY29sb3VyPSdncmV5ODAnKSArCiAgICB0aGVtZV9jYW1wcm90KGJhc2Vfc2l6ZT0xMikgKwogICAgZmFjZXRfd3JhcCh+Y29tcGFyaXNvbiwgc2NhbGVzPSdmcmVlX3knKSArCiAgICBnZW9tX2hsaW5lKGFlcyh5aW50ZXJjZXB0PWxvZzIoZXhwZWN0ZWQpKSwKICAgICAgICAgICAgICAgZGF0YT1leHBlY3RlZFtleHBlY3RlZCRzcGVjaWVzPT0nUy5jZXJldmlzaWFlJyxdLAogICAgICAgICAgICAgICBjb2xvdXI9J2JsYWNrJywgbGluZXR5cGU9MikgKwogICAgeGxhYignVGFnIGludGVuc2l0eSAobG9nMiknKSArCiAgICB5bGFiKCdEaWZmZXJlbmNlIGluIGludGVuc2l0eSAobG9nMiknKSArCiAgICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1jKGdldF9jYXRfcGFsZXR0ZSg3KSksCiAgICAgICAgICAgICAgICAgICAgICAgIG5hbWU9J0lzb2xhdGlvbiBpbnRlcmZlcmVuY2UgKCUpJykgKwogICAgZ2d0aXRsZSh4KQogIAogIHByaW50KHApCiAgcHJpbnQocCArIGdlb21fc21vb3RoKGFlcyhjb2xvdXI9YmlubmVkX2ludGVyZmVyZW5jZSksIHNlPUZBTFNFLCBzaXplPTAuNSkpCiAgcHJpbnQocCAlKyUgdG9fcGxvdF9pbnRlcmZlcmVuY2VfZmx0ICsKICAgICAgICAgIGdlb21fc21vb3RoKGFlcyhjb2xvdXI9YmlubmVkX3EpLCBzZT1GQUxTRSwgc2l6ZT0wLjUpICsKICAgICAgICAgIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWMoZ2V0X2NhdF9wYWxldHRlKDcpKSwKICAgICAgICAgICAgICAgICAgICAgICAgbmFtZT0nUGVyY29sYXRvciBRIHZhbHVlJykpCgogIHJldHVybihOVUxMKQp9KQpgYGAKTGV0J3MgcGxvdCB0aGUgaW50ZW5zaXR5IHZzIGRpZmZlcmVuY2UgaW4gaW50ZW5zaXR5IGZvciBhbGwgY29tcGFyaXNvbnMgYW5kIGZpbHRlcmluZyBzY2hlbWVzLgpgYGB7cn0KcXVhbnRfdnNfbWVhbiAlPiUgbmFtZXMoKSAlPiUgbGFwcGx5KGZ1bmN0aW9uKHgpewogIAogICAgdG1wX2RhdGEgPC0gIHF1YW50X3ZzX21lYW5bW3hdXSAlPiUKICAgICAgZmlsdGVyKHNwZWNpZXMhPSdtaXhlZCcsICFjb21wYXJpc29uICVpbiUgcG9zaXRpdmVfY29tcGFyaXNvbnMpCiAgICAKICAgIHAgPC0gdG1wX2RhdGEgJT4lCiAgICAgIGdncGxvdChhZXMobG9nMihpbnRlbnNpdHkpLCBkaWZmKSkgKwogICAgICBnZW9tX3BvaW50KHNpemU9MC4wNSwgYWxwaGE9MC4wNSwgY29sb3VyPSdncmV5MTAnKSArCiAgICAgIGdlb21fZGVuc2l0eTJkKHNpemU9MC4zLCBjb2xvdXI9Z2V0X2NhdF9wYWxldHRlKDIpWzJdKSArCiAgICAgIHRoZW1lX2NhbXByb3QoYmFzZV9zaXplPTEyKSArCiAgICAgIGZhY2V0X2dyaWQoc3BlY2llc35jb21wYXJpc29uLCBzY2FsZXM9J2ZyZWVfeScpICsKICAgICAgZ2VvbV9obGluZShhZXMoeWludGVyY2VwdD1sb2cyKGV4cGVjdGVkKSksCiAgICAgICAgICAgICAgICAgZGF0YT1leHBlY3RlZFshZXhwZWN0ZWQkY29tcGFyaXNvbiAlaW4lIHBvc2l0aXZlX2NvbXBhcmlzb25zLF0sCiAgICAgICAgICAgICAgICAgY29sb3VyPSdibGFjaycsIGxpbmV0eXBlPTIpICsKICAgICAgeGxhYignVGFnIGludGVuc2l0eSAobG9nMiknKSArCiAgICAgIHlsYWIoJ0RpZmZlcmVuY2UgaW4gaW50ZW5zaXR5IChsb2cyKScpICsKICAgICAgZ2d0aXRsZSh4KSArCiAgICAgIGNvb3JkX2NhcnRlc2lhbih5bGltPWMoLTQsNCkpCiAgCiAgICBwcmludChwKQoKICAgIHByaW50KHAgJSslICh0bXBfZGF0YSAlPiUKICAgICAgICAgICAgICAgICAgIGZpbHRlcihQZXJjb2xhdG9yLnEuVmFsdWU8PTAuMDA1KSkpCiAgICBwcmludChwICUrJSAodG1wX2RhdGEgJT4lCiAgICAgICAgICAgICAgICAgICBmaWx0ZXIoSXNvbGF0aW9uLkludGVyZmVyZW5jZS5pbi5QZXJjZW50PD0xMCkpKQogICAgcHJpbnQocCAlKyUgKHRtcF9kYXRhICU+JQogICAgICAgICAgICAgICAgICAgZmlsdGVyKFBlcmNvbGF0b3IucS5WYWx1ZTw9MC4wMDUsIElzb2xhdGlvbi5JbnRlcmZlcmVuY2UuaW4uUGVyY2VudDw9MTApKSkKICAgIHByaW50KHAgJSslICh0bXBfZGF0YSAlPiUKICAgICAgICAgICAgICAgICAgIGZpbHRlcihQZXJjb2xhdG9yLnEuVmFsdWU8PTAuMDA1LCBJc29sYXRpb24uSW50ZXJmZXJlbmNlLmluLlBlcmNlbnQ8PTEwLCBBdmVyYWdlLlJlcG9ydGVyLlNOPjEwKSkpCiAgICByZXR1cm4oTlVMTCkKfSkKYGBgCgoKTm93LCBsZXQncyBmaWx0ZXIgdGhlIFBTTXMgYWdhaW5zdCB0aGVzZSB0aHJlc2hvbGRzLgpgYGB7cn0KcHNtX3Jlc19mbHQgPC0gcHNtX3JlcyAlPiUgbGFwcGx5KGZ1bmN0aW9uKHgpewogIG91dCA8LSBmaWx0ZXJfVE1UX1BTTXMoeCwgaW50ZXJfdGhyZXNoPTIwLCBzbl90aHJlc2g9MCkKICAKICBvdXQgPC0gb3V0W2ZEYXRhKG91dCkkUGVyY29sYXRvci5xLlZhbHVlPD0wLjAwMSxdCiAgY2FtcHJvdFI6OjptZXNzYWdlX3BhcnNlKGZEYXRhKG91dCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICdNYXN0ZXIuUHJvdGVpbi5BY2Nlc3Npb25zJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIlBlcmNvbGF0b3IgUSB2YWx1ZSBmaWx0ZXJpbmciKQogIG91dAp9KQoKcHNtX3Jlc19mbHRfc24gPC0gcHNtX3Jlc19mbHQgJT4lIGxhcHBseShmdW5jdGlvbih4KXsKICBvdXQgPC0gZmlsdGVyX1RNVF9QU01zKHgsIGludGVyX3RocmVzaD0yMCwgc25fdGhyZXNoPTEwKQogIG91dAp9KQpgYGAKCmBgYHtyfQoKcHNtX3JlcyAlPiUgbmFtZXMoKSAlPiUgbGFwcGx5KGZ1bmN0aW9uKHgpewogIAogIGRhdGFzZXRzIDwtIGxpc3QoJ3VuZmlsdGVyZWQnPXBzbV9yZXMsICdmaWx0ZXJlZCc9cHNtX3Jlc19mbHQsICdcbmZpbHRlcmVkLCBpbmMgUy9OJz1wc21fcmVzX2ZsdF9zbikKICAKICBmb3IoZGF0YXNldCBpbiBuYW1lcyhkYXRhc2V0cykpewogICAgCiAgICBhbGwgPC0gZGF0YXNldHNbW2RhdGFzZXRdXVtbeF1dCiAgICBocyA8LSBhbGxbZkRhdGEoYWxsKSRzcGVjaWVzPT0nSC5zYXBpZW5zJ10KICAgIHNjIDwtIGFsbFtmRGF0YShhbGwpJHNwZWNpZXM9PSdTLmNlcmV2aXNpYWUnXQogICAgCiAgICBzbGljZXMgPC0gbGlzdCgnQWxsJz1hbGwsICdILnNhcGllbnMnPWhzLCAnUy5jZXJldmlzaWFlJz1zYykKICAgIGZvcihzbGljZSBpbiBuYW1lcyhzbGljZXMpKXsKICAgICAgcCA8LSBzbGljZXNbW3NsaWNlXV0gJT4lIHBsb3RfVE1UX25vdGNoKCkgKwogICAgICAgIGdndGl0bGUoc3ByaW50ZignJXMgLSAlcyAtICVzJywgeCwgc2xpY2UsIGRhdGFzZXQpKQogICAgICBwcmludChwKQogICAgfQogIH0KICAKICByZXR1cm4oTlVMTCkKICAKfSkKCmBgYAoKUGVyLXRhZyBub3RjaCBwbG90cwpgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9OH0KCnBzbV9yZXMgJT4lIG5hbWVzKCkgJT4lIGxhcHBseShmdW5jdGlvbih4KXsKICAKICBkYXRhc2V0cyA8LSBsaXN0KCd1bmZpbHRlcmVkJz1wc21fcmVzLCAnZmlsdGVyZWQnPXBzbV9yZXNfZmx0LCAnXG5maWx0ZXJlZCwgaW5jIFMvTic9cHNtX3Jlc19mbHRfc24pCiAgCiAgZm9yKGRhdGFzZXQgaW4gbmFtZXMoZGF0YXNldHMpKXsKICAgIAogICAgYWxsIDwtIGRhdGFzZXRzW1tkYXRhc2V0XV1bW3hdXQogICAgaHMgPC0gYWxsW2ZEYXRhKGFsbCkkc3BlY2llcz09J0guc2FwaWVucyddCiAgICBzYyA8LSBhbGxbZkRhdGEoYWxsKSRzcGVjaWVzPT0nUy5jZXJldmlzaWFlJ10KICAgIAogICAgc2xpY2VzIDwtIGxpc3QoJ0FsbCc9YWxsLCAnSC5zYXBpZW5zJz1ocywgJ1MuY2VyZXZpc2lhZSc9c2MpCiAgICBmb3Ioc2xpY2UgaW4gbmFtZXMoc2xpY2VzKSl7CiAgICAgIHAgPC0gc2xpY2VzW1tzbGljZV1dICU+JSBwbG90X1RNVF9ub3RjaChmYWNldF9ieV9zYW1wbGU9VFJVRSkgKwogICAgICAgIGdndGl0bGUoc3ByaW50ZignJXMgLSAlcyAtICVzJywgeCwgc2xpY2UsIGRhdGFzZXQpKQogICAgICBwcmludChwKQogICAgfQogIH0KICAKICByZXR1cm4oTlVMTCkKICAKfSkKYGBgCgpUYWxsaWVzIGZvciBmcmFjdGlvbiBzdWItbm90Y2ggUFNNcyBwZXIgcHJvdGVpbgpgYGB7cn0KCnBzbV9yZXMgJT4lIG5hbWVzKCkgJT4lIGxhcHBseShmdW5jdGlvbih4KXsKICAKICBkYXRhc2V0cyA8LSBsaXN0KCd1bmZpbHRlcmVkJz1wc21fcmVzLCAnZmlsdGVyZWQnPXBzbV9yZXNfZmx0LCAnXG5maWx0ZXJlZCwgaW5jIFMvTic9cHNtX3Jlc19mbHRfc24pCiAgCiAgZm9yKGRhdGFzZXQgaW4gbmFtZXMoZGF0YXNldHMpKXsKICAgIAogICAgYWxsIDwtIGRhdGFzZXRzW1tkYXRhc2V0XV1bW3hdXQogICAgaHMgPC0gYWxsW2ZEYXRhKGFsbCkkc3BlY2llcz09J0guc2FwaWVucyddCiAgICBzYyA8LSBhbGxbZkRhdGEoYWxsKSRzcGVjaWVzPT0nUy5jZXJldmlzaWFlJ10KICAgIAogICAgc2xpY2VzIDwtIGxpc3QoJ0FsbCc9YWxsLCAnSC5zYXBpZW5zJz1ocywgJ1MuY2VyZXZpc2lhZSc9c2MpCiAgICBmb3Ioc2xpY2UgaW4gbmFtZXMoc2xpY2VzKSl7CiAgICAgIAogICAgICBub3RjaF9wZXJfcHJvdGVpbiA8LSBnZXRfbm90Y2hfcGVyX3Byb3RlaW4oc2xpY2VzW1tzbGljZV1dKSAlPiUKICAgICAgICBmaWx0ZXIoZnJhY3Rpb25fYmVsb3c+MCkKICAgICAgCiAgICAgIHAgPC0gcGxvdF9mcmFjdGlvbl9iZWxvd19ub3RjaF9wZXJfcHJvdChub3RjaF9wZXJfcHJvdGVpbikgKwogICAgICAgIGdndGl0bGUoc3ByaW50ZignJXMgLSAlcyAtICVzJywgeCwgc2xpY2UsIGRhdGFzZXQpKQogICAgICAKICAgICAgcHJpbnQocCkKICAgICAgfQogIH0KICAKICByZXR1cm4oTlVMTCkKICAKfSkKCgpgYGAKCgpNaXNzaW5nIHZhbHVlcyBmcmVxdWVuY2llcy4KYGBge3J9CnBzbV9yZXMgJT4lIG5hbWVzKCkgJT4lIGxhcHBseShmdW5jdGlvbih4KXsKICAKICBkYXRhc2V0cyA8LSBsaXN0KCd1bmZpbHRlcmVkJz1wc21fcmVzLCAnZmlsdGVyZWQnPXBzbV9yZXNfZmx0LCAnXG5maWx0ZXJlZCwgaW5jIFMvTic9cHNtX3Jlc19mbHRfc24pCiAgCiAgZm9yKGRhdGFzZXQgaW4gbmFtZXMoZGF0YXNldHMpKXsKICAgIAogICAgYWxsIDwtIGRhdGFzZXRzW1tkYXRhc2V0XV1bW3hdXQogICAgaHMgPC0gYWxsW2ZEYXRhKGFsbCkkc3BlY2llcz09J0guc2FwaWVucyddCiAgICBzYyA8LSBhbGxbZkRhdGEoYWxsKSRzcGVjaWVzPT0nUy5jZXJldmlzaWFlJ10KICAgIAogICAgc2xpY2VzIDwtIGxpc3QoJ0FsbCc9YWxsLCAnSC5zYXBpZW5zJz1ocywgJ1MuY2VyZXZpc2lhZSc9c2MpCiAgICBmb3Ioc2xpY2UgaW4gbmFtZXMoc2xpY2VzKSl7CiAgICAgIHBsb3ROQShzbGljZXNbW3NsaWNlXV0sIHBOQSA9IDApCiAgICB9CiAgfQogIAogIHJldHVybihOVUxMKQogIAp9KQoKYGBgCgpTYXZlIG91dCBvYmplY3RzIGZvciBkb3duc3RyZWFtIG5vdGVib29rcwpgYGB7cn0Kc2F2ZVJEUyhxdWFudF92c19tZWFuLCAnLi4vcmVzdWx0cy9xdWFudF92c19tZWFuLnJkcycpCnNhdmVSRFMocHNtX3Jlc19mbHQsICcuLi9yZXN1bHRzL3BzbV9yZXNfZmx0LnJkcycpCnNhdmVSRFMocHNtX3Jlc19mbHRfc24sICcuLi9yZXN1bHRzL3BzbV9yZXNfZmx0X3NuLnJkcycpCnNhdmVSRFMoZXhwZWN0ZWQsICcuLi9yZXN1bHRzL2V4cGVjdGVkLnJkcycpCmBgYAoKCg==